﻿//The MIT License(MIT)

//Copyright(c) 2015 Martin Chrzan

//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Schema;

namespace XmlAutoComplete
{
    public class XsdParser : IParser
    {
        private string XsdPath { get; set; }
        public XsdParser(string path)
        {
            XsdPath = path;
        }

        private List<XmlSchemaComplexType> ComplexSchemaTypes { get;set; }
        private List<XmlSchemaSimpleType> SimpleSchemaTypes { get;set; }

        public DecisionTree GetTree()
        {
            DecisionTree tree = new DecisionTree();

            
            //first element has no parent 
            tree.Parent = null;


            // Add the customer schema to a new XmlSchemaSet and compile it. 
            // Any schema validation warnings and errors encountered reading or  
            // compiling the schema are handled by the ValidationEventHandler delegate.
            XmlSchemaSet schemaSet = new XmlSchemaSet();
            schemaSet.ValidationEventHandler += new ValidationEventHandler(ValidationCallback);

            XmlTextReader reader = new XmlTextReader(XsdPath);
            XmlSchema myschema = XmlSchema.Read(reader, ValidationCallback);

            schemaSet.Add(myschema);
            schemaSet.Compile();

            // Retrieve the compiled XmlSchema object from the XmlSchemaSet 
            // by iterating over the Schemas property.
            XmlSchema schema = null;

            schema = schemaSet.Schemas().OfType<XmlSchema>().FirstOrDefault();

            ComplexSchemaTypes = schema.SchemaTypes.Values.OfType<XmlSchemaComplexType>().ToList();
            SimpleSchemaTypes = schema.SchemaTypes.Values.OfType<XmlSchemaSimpleType>().ToList();

            if (schema.Elements != null && schema.Elements.Count > 0)
            {
                //take only first element this will be root - if there are any on the same level 
                //it is wrong as there should be only one element and probably they are referenced inside first element
                //so they will be loaded with the first one
                var element = schema.Elements.Values.OfType<XmlSchemaElement>().FirstOrDefault();

                if (element != null)
                {
                    tree.Name = element.Name;
                    tree.Namespace = element.QualifiedName.Namespace;
                    tree.MaxCount = element.MaxOccurs;

                    // Get the complex type of the Customer element.
                    XmlSchemaComplexType complexType = element.ElementSchemaType as XmlSchemaComplexType;

                    // If the complex type has any attributes, get an enumerator  
                    // and write each attribute name to the console. 
                    if (complexType.AttributeUses.Count > 0)
                    {
                        tree.Attributes = new List<Attribute>();
                        IDictionaryEnumerator enumerator =
                            complexType.AttributeUses.GetEnumerator();

                        while (enumerator.MoveNext())
                        {
                            XmlSchemaAttribute attribute =
                                (XmlSchemaAttribute)enumerator.Value;

                            tree.Attributes.Add(new Attribute() { Name = attribute.Name, Values = GetAttributeValues(attribute) });
                        }
                    }

                    // Get the sequence particle of the complex type.
                    XmlSchemaSequence sequence = complexType.ContentTypeParticle as XmlSchemaSequence;

                    tree.Childs = new List<DecisionTree>();

                    if (sequence != null)
                    {
                        // Iterate over each XmlSchemaElement in the Items collection. 
                        foreach (XmlSchemaElement childElement in sequence.Items.OfType<XmlSchemaElement>())
                        {
                            var result = IterateElement(childElement, tree);
                            if (result != null)
                                tree.Childs.Add(result);
                        }
                    }
                }
            }

            //starting point - level up of root element
            DecisionTree parentTree = new DecisionTree();
            parentTree.Childs = new List<DecisionTree>();
            parentTree.Childs.Add(tree);

            return parentTree;
        }


        public DecisionTree IterateElement(XmlSchemaElement element, DecisionTree parent, bool schemaType=false)
        {
            DecisionTree leaf = new DecisionTree();

            if (!schemaType)
            {
                leaf.Name = element.QualifiedName.Name;
                leaf.Namespace = element.QualifiedName.Namespace;
                leaf.MaxCount = element.MaxOccurs;
            }
            else
            {
                if (element.SchemaType.BaseXmlSchemaType != null)
                {
                    leaf.Abstract = true;
                    leaf.BaseName = element.SchemaType.BaseXmlSchemaType.Name;
                    leaf.Name = element.SchemaType.Name;
                    leaf.Namespace = element.SchemaType.QualifiedName.Namespace;
                    leaf.MaxCount = element.MaxOccurs;
                }
            }

            leaf.Parent = parent;

            XmlSchemaType type = element.ElementSchemaType;
            if (schemaType)
                type = element.SchemaType;

            if (type is XmlSchemaComplexType)
            {
                // Get the complex type of the Customer element.
                XmlSchemaComplexType complexType = type as XmlSchemaComplexType;

                //find types that implements this element base
                if (complexType.IsAbstract)
                {
                    parent.Childs.AddRange(GetImplementationOfType(complexType,  parent));
                    return null;
                }

                // If the complex type has any attributes, get an enumerator  
                // and write each attribute name to the console. 
                if (complexType.AttributeUses.Count > 0)
                {
                    leaf.Attributes = new List<Attribute>();
                    IDictionaryEnumerator enumerator =
                        complexType.AttributeUses.GetEnumerator();

                    while (enumerator.MoveNext())
                    {
                        XmlSchemaAttribute attribute =
                            (XmlSchemaAttribute)enumerator.Value;


                        leaf.Attributes.Add(new Attribute() { Name = attribute.Name, Values = GetAttributeValues(attribute) });
                    }
                }

                // Get the sequence particle of the complex type.
                XmlSchemaSequence sequence = complexType.ContentTypeParticle as XmlSchemaSequence;



                if (sequence != null)
                {
                    leaf.Childs = new List<DecisionTree>();
                    foreach (XmlSchemaElement item in sequence.Items.OfType<XmlSchemaElement>())
                    {
                        var elem = IterateElement(item, leaf);
                        if(elem != null)
                          leaf.Childs.Add(elem);
                    }
                }

            }
            return leaf;
        }

        static void ValidationCallback(object sender, ValidationEventArgs args)
        {
            if (args.Severity == XmlSeverityType.Warning)
                Console.Write("WARNING: ");
            else if (args.Severity == XmlSeverityType.Error)
                Console.Write("ERROR: ");

            Console.WriteLine(args.Message);
        }

        private List<string> GetAttributeValues(XmlSchemaAttribute attribute)
        {
            var values = new List<string>();

            switch (attribute.AttributeSchemaType.TypeCode)
            {
                case XmlTypeCode.Boolean:
                    values.Add("true");
                    values.Add("false");
                    break;
                case XmlTypeCode.String:
                    if (attribute.AttributeSchemaType is XmlSchemaSimpleType)
                    {
                        var simpleType = attribute.AttributeSchemaType as XmlSchemaSimpleType;
                        values = GetValues(simpleType.Content);
                    }
                    break;

            }

            return values;
        }


        /// <summary>
        /// Get list of values that are allowed for given attribute if attribute type is XmlSchemaSimpleType and AttributeSchemaType.TypeCode is string
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        private List<string> GetValues(XmlSchemaSimpleTypeContent content)
        {
            var values = new List<string>();

            if (content == null) return values;

            //union of several simple types that can have some of these types again inside, so call it recursively
            if (content is XmlSchemaSimpleTypeUnion)
            {
                var union = content as XmlSchemaSimpleTypeUnion;
                foreach (var item in union.BaseTypes)
                {
                    if (item is XmlSchemaSimpleType)
                    {
                        var simpleType = item as XmlSchemaSimpleType;
                        values.AddRange(GetValues(simpleType.Content));
                    }
                }
            }
            else if(content is XmlSchemaSimpleTypeList)
            {
                //not supported yet as list allow to add several items in element value (opposite of a type restriction)
            }
            else if(content is XmlSchemaSimpleTypeRestriction)
            {
                var restriction = content as XmlSchemaSimpleTypeRestriction;
                foreach (var facet in restriction.Facets.OfType<XmlSchemaEnumerationFacet>())
                {
                    values.Add(facet.Value);
                }
            }

            return values;
        }


        /// <summary>
        /// Get list of elements that are extension of provided base type
        /// </summary>
        /// <param name="baseType"></param>
        /// <param name="parentTree"></param>
        /// <returns></returns>
        private List<DecisionTree> GetImplementationOfType(XmlSchemaComplexType baseType, DecisionTree parentTree)
        {
            var result = new List<DecisionTree>();

            foreach (var item in ComplexSchemaTypes)
            {
                if (item.BaseXmlSchemaType == baseType)
                {
                    XmlSchemaElement element = new XmlSchemaElement() { SchemaType = item, SchemaTypeName = item.QualifiedName };

                    var newElement = IterateElement(element, parentTree, true);
                    if (newElement != null)
                        result.Add(newElement);
                }
            }


            return result;
        }

       
    }
 }
